Completed
Push — master ( 7b524c...0d3c7c )
by Ruben de
05:15 queued 02:24
created

SizeEstimation.calculateOutputsSize   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 1
dl 0
loc 4
rs 10
nop 1
1
var assert = require('assert');
2
var bitcoin = require('bitcoinjs-lib');
3
4
var SizeEstimation = {
5
    SIZE_DER_SIGNATURE: 72,
6
    SIZE_V0_P2WSH: 36
7
};
8
9
SizeEstimation.getPublicKeySize = function(isCompressed) {
10
    return isCompressed ? 33 : 65;
11
};
12
13
SizeEstimation.getLengthForScriptPush = function(length) {
14
    if (length < 75) {
15
        return 1;
16
    } else if (length <= 0xff) {
17
        return 2;
18
    } else if (length <= 0xffff) {
19
        return 3;
20
    } else if (length <= 0xffffffff) {
21
        return 5;
22
    } else {
23
        throw new Error("Size of pushdata too large");
24
    }
25
};
26
27
SizeEstimation.getLengthForVarInt = function(length) {
28
    if (length < 253) {
29
        return 1;
30
    }
31
32
    // Rest have a prefix byte
33
    var numBytes;
34
    if (length < 65535) {
35
        numBytes = 2;
36
    } else if (length < 4294967295) {
37
        numBytes = 4;
38
    } else if (length < 18446744073709551615) {
39
        numBytes = 8;
40
    } else {
41
        throw new Error("Size of varint too large");
42
    }
43
44
    return 1 + numBytes;
45
};
46
47
SizeEstimation.estimateMultisigStackSize = function(m, keys) {
48
    // Initialize with OP_0
49
    var stackSizes = [0];
50
    var i;
51
    for (i = 0; i < m; i++) {
52
        stackSizes.push(SizeEstimation.SIZE_DER_SIGNATURE);
53
    }
54
55
    var scriptSize = 1; // OP_$m
56
    for (i = 0; i < keys.length; i++) {
57
        scriptSize += this.getLengthForScriptPush(keys[i].length) + keys[i].length;
58
    }
59
    scriptSize += 2; // OP_$n OP_CHECKMULTISIG
60
    return [stackSizes, scriptSize];
61
};
62
63
64
SizeEstimation.estimateMultisigStackSize = function(m, keys) {
65
    // Initialize with OP_0
66
    var stackSizes = [0];
67
    var i;
68
    for (i = 0; i < m; i++) {
69
        stackSizes.push(SizeEstimation.SIZE_DER_SIGNATURE);
70
    }
71
72
    var scriptSize = 1; // OP_$m
73
    for (i = 0; i < keys.length; i++) {
74
        scriptSize += this.getLengthForScriptPush(keys[i].length) + keys[i].length;
75
    }
76
    scriptSize += 2; // OP_$n OP_CHECKMULTISIG
77
    return [stackSizes, scriptSize];
78
};
79
80
SizeEstimation.estimateP2PKStackSize = function(key) {
81
    var stackSizes = [SizeEstimation.SIZE_DER_SIGNATURE];
82
    var scriptSize = this.getLengthForScriptPush(key.length) + key.length + 1; // KEY OP_CHECKSIG
83
84
    return [stackSizes, scriptSize];
85
};
86
87
SizeEstimation.estimateP2PKHStackSize = function(isCompressed) {
88
    if (typeof isCompressed === 'undefined') {
89
        isCompressed = true;
90
    }
91
92
    var stackSizes = [this.SIZE_DER_SIGNATURE, this.getPublicKeySize(isCompressed)];
93
    var scriptSize = 2 + this.getLengthForScriptPush(20) + 2;
94
95
    return [stackSizes, scriptSize];
96
};
97
98
/**
99
 * As pure a function as it gets, but without the overhead
100
 * of checking everything. Make sure your calls are correct.
101
 *
102
 * @param {Buffer} stackSizes
103
 * @param {Buffer} isWitness
104
 * @param {Buffer} rs
105
 * @param {Buffer} ws
106
 */
107
SizeEstimation.estimateStackSignatureSize = function(stackSizes, isWitness, rs, ws) {
108
    assert(ws === null || isWitness);
109
110
    var scriptSigSizes = [];
111
    var witnessSizes = [];
112
    if (isWitness) {
113
        witnessSizes = stackSizes;
114
        if (ws instanceof Buffer) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
115
            witnessSizes.push(ws.length);
116
        }
117
    } else {
118
        scriptSigSizes = stackSizes;
119
    }
120
121
    if (rs instanceof Buffer) {
122
        scriptSigSizes.push(rs.length);
123
    }
124
125
    var self = this;
126
    var scriptSigSize = 0;
127
    scriptSigSizes.map(function(elementLen) {
128
        scriptSigSize += self.getLengthForScriptPush(elementLen) + elementLen;
129
    });
130
131
    scriptSigSize += self.getLengthForVarInt(scriptSigSize);
132
133
    var witnessSize = 0;
134
    if (witnessSizes.length > 0) {
135
        witnessSizes.map(function(elementLen) {
136
            witnessSize += self.getLengthForVarInt(elementLen) + elementLen;
137
        });
138
        witnessSize += self.getLengthForVarInt(witnessSizes.length);
139
    }
140
141
    return [scriptSigSize, witnessSize];
142
};
143
144
/**
145
 *
146
 * @param {Buffer} script - main script, can equal RS/WS too
147
 * @param {Buffer} redeemScript
148
 * @param {Buffer} witnessScript
149
 * @param {boolean} isWitness - required, covers P2WPKH and so on
150
 */
151
SizeEstimation.estimateInputFromScripts = function(script, redeemScript, witnessScript, isWitness) {
152
    assert(witnessScript === null || isWitness);
153
154
    var stackSizes;
155
    if (bitcoin.script.multisig.output.check(script)) {
156
        var multisig = bitcoin.script.multisig.output.decode(script);
157
        stackSizes = this.estimateMultisigStackSize(multisig.m, multisig.pubKeys)[0];
158
    } else if (bitcoin.script.pubKey.output.check(script)) {
159
        var p2pk = bitcoin.script.pubKey.output.decode(script);
160
        stackSizes = this.estimateP2PKStackSize(p2pk);
161
    } else if (bitcoin.script.pubKeyHash.output.check(script)) {
162
        // assumes compressed
163
        stackSizes = this.estimateP2PKHStackSize(true);
164
    } else {
165
        throw new Error("Unsupported script type");
166
    }
167
168
    return this.estimateStackSignatureSize(stackSizes, isWitness, redeemScript, witnessScript);
169
};
170
171
SizeEstimation.estimateUtxo = function(utxo) {
172
    var spk = Buffer.from(utxo.scriptpubkey_hex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
173
    var rs = typeof utxo.redeem_script === 'string' ? Buffer.from(utxo.redeem_script, 'hex') : null;
174
    var ws = typeof utxo.witness_script === 'string' ? Buffer.from(utxo.witness_script, 'hex') : null;
175
    var witness = false;
176
177
    var signScript = spk;
178
    if (bitcoin.script.scriptHash.output.check(signScript)) {
179
        if (null === rs) {
180
            throw new Error("Cant estimate, missing redeem script");
181
        }
182
        signScript = rs;
183
    }
184
185
    if (bitcoin.script.witnessPubKeyHash.output.check(signScript)) {
186
        var p2wpkh = bitcoin.script.witnessPubKeyHash.output.decode(signScript);
187
        signScript = bitcoin.script.pubKeyHash.encode(p2wpkh);
188
        witness = true;
189
    } else if (bitcoin.script.witnessScriptHash.output.check(signScript)) {
190
        if (null === ws) {
191
            throw new Error("Can't estimate, missing witness script");
192
        }
193
        signScript = ws;
194
        witness = true;
195
    }
196
197
    var types = bitcoin.script.types;
198
    var allowedTypes = [types.MULTISIG, types.P2PKH, types.P2PK];
199
    var type = bitcoin.script.classifyOutput(signScript);
200
    if (allowedTypes.indexOf(type) === -1) {
201
        throw new Error("Unsupported script type");
202
    }
203
204
    var estimation = this.estimateInputFromScripts(signScript, rs, ws, witness);
205
206
    return {
207
        scriptSig: estimation[0],
208
        witness: estimation[1]
209
    };
210
};
211
212
/**
213
 * Returns the size of input related data, given a set
214
 * of utxos we can estimate for. witness data is included
215
 * if withWitness=true, and is required for vsize/weight
216
 * calculations
217
 * @param {object} utxos
218
 * @param {boolean} withWitness
219
 * @returns {number}
220
 */
221
SizeEstimation.estimateInputsSize = function(utxos, withWitness) {
222
    var inputSize = 0;
223
    var witnessSize = 0;
224
    utxos.map(function(utxo) {
225
        var estimate = SizeEstimation.estimateUtxo(utxo);
226
        inputSize += 32 + 4 + 4 + estimate.scriptSig;
227
        if (withWitness) {
228
            witnessSize += estimate.witness;
229
        }
230
    });
231
232
    if (withWitness && witnessSize > 0) {
233
        inputSize += 2 + witnessSize;
234
    }
235
236
    return inputSize;
237
};
238
239
/**
240
 * Calculates number of bytes to serialize tx outputs
241
 * @param {Array} outs
242
 * @returns {number}
243
 */
244
SizeEstimation.calculateOutputsSize = function(outs) {
245
    var outputsSize = 0;
246
    outs.map(function(out) {
247
        var scriptSize = SizeEstimation.getLengthForVarInt(out.script.length / 2);
248
        outputsSize += 8 + scriptSize + (out.script.length / 2);
249
    });
250
    return outputsSize;
251
};
252
253
/**
254
 * Returns the transactions weight.
255
 *
256
 * @param {bitcoin.Transaction} tx
257
 * @param {array} utxos
258
 * @returns {*}
259
 */
260
SizeEstimation.estimateTxWeight = function(tx, utxos) {
261
    var outSize = SizeEstimation.calculateOutputsSize(tx.outs);
262
    var baseSize = 4 + 4 + this.estimateInputsSize(utxos, false) + 4 + outSize + 4;
263
    var witSize = 4 + 4 + this.estimateInputsSize(utxos, false) + 4 + outSize + 4;
264
    return (3 * baseSize) + witSize;
265
};
266
267
268
/**
269
 * Returns the vsize for a transaction. Same as size
270
 * for transaction with no witness data.
271
 *
272
 * @param {bitcoin.Transaction} tx
273
 * @param {array} utxos
274
 * @returns {Number}
275
 */
276
SizeEstimation.estimateTxVsize = function(tx, utxos) {
277
    return parseInt(Math.ceil(SizeEstimation.estimateTxWeight(tx, utxos) / 4), 10);
278
};
279
280
module.exports = SizeEstimation;
281